import tkinter as tk
from tkinter import messagebox

# 棋盘大小
BOARD_SIZE = 5
# 棋盘数组，初始值都为 0 表示空白
board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]
# 先手玩家，1 表示人类，2 表示机器人
first = 1
# 标记是否轮到人类下子
stop = False
# 最大搜索深度
MAX_DEPTH = 3

# 创建五子棋棋盘界面
def create_board():
    for i in range(BOARD_SIZE):
        for j in range(BOARD_SIZE):
            button = tk.Button(root, width=2, height=1)
            # 传入按钮自身对象
            button.config(command=lambda x=i, y=j, btn=button: human_move(x, y, btn))
            button.grid(row=i, column=j)

# 人类下棋的操作
def human_move(x, y, button):
    global stop
    if stop or board[x][y] != 0:
        return
    player = 1
    board[x][y] = player
    # 改变按钮文字为 O
    button.config(text="O")
    if check(x, y, player, '人类'):
        messagebox.showinfo("游戏结果", "人类获得胜利！")
        reset_game()
    else:
        stop = True
        robot_move()

# 机器人下棋的操作
def robot_move():
    global stop
    player = 2
    if first == 2:
        # 机器人先手
        player = 1
    best_score = float('-inf')
    best_move = None
    for yi in range(BOARD_SIZE):
        for xi in range(BOARD_SIZE):
            if board[yi][xi] == 0:
                board[yi][xi] = player
                score = minimax(0, False, float('-inf'), float('inf'))
                board[yi][xi] = 0
                if score > best_score:
                    best_score = score
                    best_move = (yi, xi)
    if best_move:
        y, x = best_move
        board[y][x] = player
        # 获取对应位置的按钮并改变文字为 X
        button = root.grid_slaves(row=y, column=x)[0]
        button.config(text="X")
        if check(y, x, player, '机器人'):
            messagebox.showinfo("游戏结果", "机器人获得胜利！")
            reset_game()
        else:
            stop = False  # 允许人类下子

# 极大极小算法
def minimax(depth, is_maximizing, alpha, beta):
    if depth == MAX_DEPTH or is_game_over():
        return evaluate()
    if is_maximizing:
        max_eval = float('-inf')
        for yi in range(BOARD_SIZE):
            for xi in range(BOARD_SIZE):
                if board[yi][xi] == 0:
                    player = 2 if first == 1 else 1
                    board[yi][xi] = player
                    eval = minimax(depth + 1, False, alpha, beta)
                    board[yi][xi] = 0
                    max_eval = max(max_eval, eval)
                    alpha = max(alpha, eval)
                    if beta <= alpha:
                        break
        return max_eval
    else:
        min_eval = float('inf')
        for yi in range(BOARD_SIZE):
            for xi in range(BOARD_SIZE):
                if board[yi][xi] == 0:
                    player = 1 if first == 1 else 2
                    board[yi][xi] = player
                    eval = minimax(depth + 1, True, alpha, beta)
                    board[yi][xi] = 0
                    min_eval = min(min_eval, eval)
                    beta = min(beta, eval)
                    if beta <= alpha:
                        break
        return min_eval

# 评估当前棋盘状态
def evaluate():
    player = 1
    if first == 2:
        player = 2
    score = 0
    for yn in range(BOARD_SIZE):
        for xn in range(BOARD_SIZE):
            if board[yn][xn] == player:
                score += count_streaks(yn, xn, player)
            elif board[yn][xn] == (3 - player):
                score -= count_streaks(yn, xn, 3 - player)
    return score

# 计算某个位置的连子得分
def count_streaks(y, x, player):
    directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
    score = 0
    for dy, dx in directions:
        count = 1
        # 正向检查
        ny, nx = y + dy, x + dx
        while 0 <= ny < BOARD_SIZE and 0 <= nx < BOARD_SIZE and board[ny][nx] == player:
            count += 1
            ny += dy
            nx += dx
        # 反向检查
        ny, nx = y - dy, x - dx
        while 0 <= ny < BOARD_SIZE and 0 <= nx < BOARD_SIZE and board[ny][nx] == player:
            count += 1
            ny -= dy
            nx -= dx
        if count == 2:
            score += 1
        elif count == 3:
            score += 10
        elif count == 4:
            score += 100
        elif count >= 5:
            score += 1000
    return score

# 检查游戏是否结束
def is_game_over():
    for yi in range(BOARD_SIZE):
        for xi in range(BOARD_SIZE):
            if board[yi][xi] != 0:
                if check(yi, xi, board[yi][xi], '', True):
                    return True
    return False

# 检查是否五子连珠
def check(y, x, player, player_name, is_simulation=False):
    directions = [(0, 1), (1, 0), (1, 1), (1, -1)]
    for dy, dx in directions:
        count = 1
        # 正向检查
        ny, nx = y + dy, x + dx
        while 0 <= ny < BOARD_SIZE and 0 <= nx < BOARD_SIZE and board[ny][nx] == player:
            count += 1
            ny += dy
            nx += dx
        # 反向检查
        ny, nx = y - dy, x - dx
        while 0 <= ny < BOARD_SIZE and 0 <= nx < BOARD_SIZE and board[ny][nx] == player:
            count += 1
            ny -= dy
            nx -= dx
        if count >= 5:
            if not is_simulation:
                print(f'{player_name} 获得胜利！')
            return True
    return False

# 设置先手
def first_player(n):
    global first
    first = n

# 重置游戏
def reset_game():
    global board, stop
    board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]
    stop = False
    # 重置所有按钮的文本
    for i in range(BOARD_SIZE):
        for j in range(BOARD_SIZE):
            button = root.grid_slaves(row=i, column=j)[0]
            button.config(text="")

# 开始游戏
def start_game():
    reset_game()
    if first == 2:
        robot_move()

# 创建主窗口
root = tk.Tk()
root.title("五子棋 - 博弈算法（最大最小定理）")

# 创建先手选择框
mainMenu = tk.Menu(root)
menu_exe = tk.Menu(mainMenu, tearoff=0)
mainMenu.add_cascade(label="游戏", menu=menu_exe)
menu_exe.add_command(label="机器人先手", command=lambda: first_player(2))
menu_exe.add_command(label="人类先手", command=lambda: first_player(1))
menu_exe.add_command(label="重新开始", command=start_game)
root.config(menu=mainMenu)

# 创建棋盘
create_board()
# 开始游戏
start_game()
root.mainloop()